package dal.datastore;

import java.util.ArrayList;
import java.util.Vector;
import java.util.regex.Pattern;

import controller.EOperatorType;
import controller.QueryParser;
import dto.ColumnDTO;
import dto.ConditionStatementDTO;
import dto.RowDTO;

public class TableData {
	private String tableName;
	private ArrayList<ColumnDTO> columns;
	private ArrayList<RowDTO> rows;

	public TableData(String name) {
		this.tableName = name;
		this.rows = new ArrayList<RowDTO>();
		this.columns = new ArrayList<ColumnDTO>();
	}

	public TableData(String name, ArrayList<RowDTO> row) {
		this.tableName = name;
		this.rows = row;
		this.columns = new ArrayList<ColumnDTO>();
	}

	public TableData(String name, ArrayList<RowDTO> row,
			ArrayList<ColumnDTO> column) {
		this.tableName = name;
		this.rows = row;
		this.columns = column;
	}

	public ArrayList<RowDTO> getRows() {
		return this.rows;
	}

	public String getTableName() {
		return this.tableName;
	}

	public void setTableName(String name) {
		this.tableName = name;
	}

	public ArrayList<ColumnDTO> getColumns() {
		return this.columns;
	}

	/**
	 * Get column at index
	 * 
	 * @param index
	 * @return ColumnDTO
	 * @author Stanley
	 */
	public ColumnDTO getColumnAt(int index) {
		return this.columns.get(index);
	}

	public void setRows(ArrayList<RowDTO> rows) {
		this.rows = rows;
	}

	public void setColumns(ArrayList<ColumnDTO> column) {
		this.columns = column;
	}

	public void addRow(RowDTO row) {
		this.rows.add(row);
	}
	
	private void setRowAt(int index, RowDTO row){
		this.rows.add(index, row);
		this.rows.remove(index + 1);
	}
	/**
	 * Update rows with theirs indexes.
	 * @param indexes
	 * @param rows
	 * @return
	 */
	public void updateRows(ArrayList<Integer> indexes, ArrayList<RowDTO> rows){
		try {
			if(indexes.size() != 0){
				int i = 0;
				for (int index : indexes) {
					setRowAt(index, rows.get(i));
					i++;
				}
			} else {
				
			}
		} catch (Exception e) {
			throw e;
		}
	}

	/**
	 * Set table data with columns
	 * 
	 * @param cols
	 * @return TableData
	 * @author Stanley
	 * @throws Exception
	 */
	public TableData selectDataWithColumns(ArrayList<String> cols)
			throws Exception {
		// Initialize
		Vector<Integer> columnIndex = new Vector<>();
		// 1. Validate columns
		Vector<ColumnDTO> vectorColumns = new Vector<>(this.columns);
		for (String col : cols) {
			int index = vectorColumns.lastIndexOf(new ColumnDTO(col, null));
			if (index == -1) {
				throw new Exception("Column \"" + col + "\" is not exists");
			}
			columnIndex.add(index);
		}

		// select data
		TableData temp = new TableData("temp");
		// 2. Add columns
		for (int index : columnIndex) {
			temp.getColumns().add(this.getColumnAt(index));
		}
		// 2. Add rows
		for (RowDTO row : this.rows) {
			ArrayList<Object> tempRow = new ArrayList<>();
			for (int index : columnIndex) {
				tempRow.add(row.getValueAt(index));
			}
			temp.getRows().add(new RowDTO(tempRow));
		}

		return temp;
	}

	/**
	 * Select data with conditions
	 * 
	 * @param and_or
	 *            Array AND OR condition
	 * @param conditions
	 *            ATOM condition Example : a=1, a='a'
	 * @return TableData
	 * @throws Exception
	 * @author Stanley
	 * @param indexes 
	 */
	public TableData selectDataWithConditions(ArrayList<String> and_or,
			ArrayList<ConditionStatementDTO> conditions, ArrayList<Integer> indexes) throws Exception {
		// 1. Initialize
		TableData temp = new TableData("temp");
		temp.setColumns(this.columns);
		Vector<ColumnDTO> vectorColumns = new Vector<>(this.columns);
		// If conditions is an atom statement.
		if (and_or.size() == 0) {
			// 2. Validate left operand
			ConditionStatementDTO condition = conditions.get(0);
			int index = vectorColumns.lastIndexOf(new ColumnDTO(condition
					.getLeftOperand().toString(), null));
			if (index == -1) {
				throw new Exception("Column \""
						+ condition.getLeftOperand().toString()
						+ "\" is not exists");
			}

			// 3.Select rows
			for (int i = 0; i < this.rows.size(); i ++) {
				RowDTO row = this.rows.get(i);
				condition.setLeftOperand(row.getValueAt(index));
				if (condition.computeCondition()) {
					indexes.add(i);
					temp.getRows().add(row);
				}
			}
			return temp;
		} else if (and_or.size() == 1) {
			// Get connector
			String connector = and_or.get(0);

			// Get condition statement
			ConditionStatementDTO condition1 = conditions.get(0);
			ConditionStatementDTO condition2 = conditions.get(1);

			// Get index
			int index1 = vectorColumns.lastIndexOf(new ColumnDTO(condition1
					.getLeftOperand().toString(), null));
			int index2 = vectorColumns.lastIndexOf(new ColumnDTO(condition2
					.getLeftOperand().toString(), null));
			if (index1 == -1) {
				throw new Exception("Column \""
						+ condition1.getLeftOperand().toString()
						+ "\" is not exists");
			}
			if (index2 == -1) {
				throw new Exception("Column \""
						+ condition2.getLeftOperand().toString()
						+ "\" is not exists");
			}
			switch (connector) {
			case "and":
				for (int i = 0; i < this.rows.size(); i ++) {
					RowDTO row = this.rows.get(i);
					condition1.setLeftOperand(row.getValueAt(index1));
					condition2.setLeftOperand(row.getValueAt(index2));
					if (condition1.computeCondition()
							&& condition2.computeCondition()) {
						indexes.add(i);
						temp.getRows().add(row);
					}
				}
				return temp;
			case "or":
				for (int i = 0; i < this.rows.size(); i ++) {
					RowDTO row = this.rows.get(i);
					condition1.setLeftOperand(row.getValueAt(index1));
					condition2.setLeftOperand(row.getValueAt(index2));
					if (condition1.computeCondition()
							|| condition2.computeCondition()) {
						indexes.add(i);
						temp.getRows().add(row);
					}
				}
				return temp;
			default:
				break;
			}
		}
		return temp;
	}

	/**
	 * Descartes multiply
	 * 
	 * @param table
	 * @return
	 * @author Stanley
	 */
	public TableData DescartesMultiply(TableData table) {
		if (table == null) {
			return this;
		}
		if (this.getRows().size() == 0) {
			return table;
		}
		TableData temp = new TableData("temp");

		// 1. Add columns
		temp.getColumns().addAll(this.columns);
		temp.getColumns().addAll(table.getColumns());

		// 2. multiple row
		for (RowDTO row : this.rows) {
			for (RowDTO tableRow : table.getRows()) {
				temp.getRows().add(row.addElements(tableRow));
			}
		}
		return temp;
	}

	/**
	 * Join table
	 * @param table
	 * @param join
	 * @return TableData
	 * @throws Exception 
	 */
	public TableData join(TableData table, String join) throws Exception{
		// 1. Parse join statement
		Pattern p = Pattern.compile("(\\.|=)");
		ArrayList<String> strings  = QueryParser.removeAllEmptyString(p.split(join));
		if (!strings.get(0).equalsIgnoreCase(this.tableName) || !strings.get(2).equalsIgnoreCase(table.getTableName())) {
			throw new Exception ("Alias is incorrect");
		}
		
		TableData temp = this.DescartesMultiply(table);
		
		ConditionStatementDTO condition = new ConditionStatementDTO(strings.get(1), strings.get(3), EOperatorType.EQUALS);
		
		TableData joinResult = new TableData("Join Result");
		joinResult.setColumns(temp.getColumns());
		
		// 2. Get indexes
		Vector<ColumnDTO> vectorColumns1 = new Vector<>(this.columns);
		Vector<ColumnDTO> vectorColumns2 = new Vector<>(table.getColumns());
		
		int index1 = vectorColumns1.indexOf(new ColumnDTO(strings.get(1), null));
		if (index1 == -1) {
			throw new Exception("Column \"" + strings.get(1) + "\" in table " + strings.get(0) + "is not exists");
		}
		
		int index2 = vectorColumns2.indexOf(new ColumnDTO(strings.get(3), null));
		if (index2 == -1) {
			throw new Exception("Column \"" + strings.get(3) + "\" in table " + strings.get(2) + " is not exists");
		}

		// Set index suitable with temp columns
		index2 += this.columns.size();
		// 3. Select rows
		for (RowDTO row : temp.getRows()) {
			condition.setLeftOperand(row.getValueAt(index1));
			condition.setRightOperand(row.getValueAt(index2));
			if (condition.computeCondition()) {
				joinResult.addRow(row);
			}
		}
		return joinResult;
	}
	@Override
	public boolean equals(Object obj) {
		try {
			if (obj != null) {
				return ((TableData) obj).getTableName().equalsIgnoreCase(
						this.tableName);
			}
			return false;
		} catch (Exception e) {
			return false;
		}
	}
}
